# -*- coding:utf-8 -*-
from server.action.batchBase import batchBase
from server.util import sqlBuild, stringUtil


class besd(batchBase):  # 光、湿、温
    def __init__(self, db, re):
        super(besd, self).__init__(db, re)
        self.besd_decision = {
            "start": self.need_switch,
            "have_task": self.have_task,
            "in_se_task": self.in_se_task,
            "lc_handler": self.lc_handler,
            "done": self.done
        }

    @batchBase.idChecked
    def action(self, data, d):  # 基站信息存储，以及光感判断
        baId, c, l, t, h = data[0:5]  # 基站id, 命令(不需要), 光感，湿度，温度
        self.insert_data(baId, l, t, h)  # 存入光温湿
        self.passivity(baId, l)  # 光感判断，任务写入

        d.callback((2,))

    def insert_data(self, baId, l, t, h):
        sql = sqlBuild.sqlDict["insert_daily_storage"]
        lSql = sql % ('brightness', baId, l)
        tSql = sql % ('temperature', baId, t)
        hSql = sql % ('humidity', baId, h)

        self.db.insert(lSql, hSql, tSql)

    # TODO 光感钝化，补正，算法修正
    def passivity(self, baId, l):  # 光感钝化，补正
        lb = stringUtil.redis_key_name(baId)
        seId = self.re.lindex(lb, 0)  # 获取路段id
        # ls = self.db.select(sqlBuild.sqlDict["select_brightness_antGain"] % seId)  # 取一个路段最近一段时间的光感
        # if ls:  # 数据存在
        if l:
            # avg = sum(int(x["data"] if x["data"] is not None else 0) for x in ls) / len(ls)  # 平均值
            # avg = float(ls[0]["d"])
            # count = avg/10
            count = int(l)/10
            if count >=6 :
                count += 1
            elif count <= 6:
                count -= 1

            if count >= 6:  # 判断光强
                self.dt("start", (baId, 0, seId))
                # self.task(baId, 0)  # 关灯
            else:
                self.dt("start", (baId, 1, seId))
                # self.task(baId, 1)  # 开灯

    def dt(self, token, args):
        """
            递归调用，伪决策树形式，每一个函数返回值对应下一个流程函数，直到返回done
            根据需求状态，决定存入什么样的任务
        :param token: 
        :param args: 
        :return: 
        """
        # args(baId, need, seId, all_task, lc_tuple)
        count, args = self.besd_decision[token](args)
        if count == "done":
            # args(baId)
            self.done(args)
        else:
            return self.dt(count, args)


    def need_switch(self, args):
        """
            1 状态节点
            检索mysql，查看是否有需要开关的路灯
        :param args: 
        :return: 
        """
        baId, need, seId = args

        sql = sqlBuild.sqlDict["select_lampClient_ergateState"] % (seId, baId, 1 if need == 0 else 0)
        lc_tuple = self.db.select(sql)  # 路段下所有与当前需求状态不一样的路灯
        if not lc_tuple:  # 没有需要开关的路灯
            return "done", baId
        return "have_task", (baId, need, seId, lc_tuple)


    def have_task(self, args):
        """
            2 状态节点
            在需要开关灯的情况下，检测所有现有任务
        :return: 
        """
        baId, need, seId, lc_tuple = args
        ss = stringUtil.redis_key_name(baId, "ss")
        rl = stringUtil.redis_key_name(baId, "rl")
        rl_task = self.re.zrange(rl, 0, -1)  # 所有已执行任务
        ss_task = self.re.zrange(ss, 0, -1)  # 所有待执行任务

        all_task = list(set(rl_task).union(set(ss_task)))  # 两个任务队列的并集

        st = lt = 0
        # 检索现有任务的形式
        for each in all_task:
            c = each
            if "seon" in c or "seof" in c:
                st = 1
                break
            elif "lnon" in c or "lnof" in c:
                lt = 1
                break

        if st == 0 and lt == 0:  # 无群发/单发任务
            return "in_se_task", (baId, need, seId, all_task)
        elif st == 1:  # 存在群发任务
            return "lc_handler", (baId, need, all_task, lc_tuple)  # 二次判断
        elif lt == 1:  # 存在单发任务
            return "done", baId

    def in_se_task(self, args):
        """
            3 状态节点
            群发任务不存在，存入群发任务
        :param args: 
        :return: 
        """
        baId, need, seId, all_task, = args
        need_new_se_task = self.pick_task(all_task, (baId, need), (seId,))
        one = list(need_new_se_task)[0]

        # TODO 开关互斥
        # 如果不存在material队列则插入，检测原因在于新插入任务可能覆盖已有任务count，造成重复插入执行
        if self.re.zscore("material", one) is None:
            self.insert_task(one)
        return "done", baId  # baId

    def lc_handler(self, args):
        """
            3 状态节点
            存在群发任务时，对单发任务的设定
        :param args: 
        :return: 
        """
        # TODO 2017/8/30 单点判断
        baId, need, all_task, lc_tuple = args
        new_lc_task = self.pick_task(all_task, (baId, need), lc_tuple)

        self.insert_task(new_lc_task)
        return "done", baId

    def done(self, baId):
        """
            决策节点
            结束开关灯流程，将所有已完成任务放回material队列
        :return: 
        """
        rl = stringUtil.redis_key_name(baId, "rl")
        rl_task = self.re.zrange(rl, 0, -1, withscores=True)  # 所有已执行任务
        self.re.delete(rl)

        self.insert_task(rl_task)


    def task(self, baId, need):
        """
        任务函数主要关心的有三部分：
            1)任务集散队列(redis-material 有序集合)
            2)待执行任务队列(redis-SS + 基站id 有序集合) 
            3)已执行任务队列(redis-RL + 基站id 有序集合)
        一.判断，是否存在与当前需求状态不一致的路灯，（需要开时，有关着的路灯）
        二.判断2)3)队列，是否存在待/已执行的任务（单发任务与群发同一时间在队列内，应只存在一种）
            1)如果是存在群发任务，则存入单发任务
        三.将已执行任务重新放回任务集散队列
        :param baId: 基站id
        :param need: 开关需求
        :return: 
        """
        lb = stringUtil.redis_key_name(baId)
        ss = stringUtil.redis_key_name(baId, "ss")
        rl = stringUtil.redis_key_name(baId, "rl")
        seId = self.re.lindex(lb, 0)  # 获取路段id

        sql = sqlBuild.sqlDict["select_lampClient_ergateState"] % (seId, baId, 1 if need == 0 else 0)
        lc_tuple = self.db.select(sql)  # 路段下所有与当前需求状态不一样的路灯
        if not lc_tuple:  # 没有需要开关的路灯
            return

        rl_task = self.re.zrange(rl, 0, -1, withscores=True)  # 所有已执行任务
        ss_task = self.re.zrange(ss, 0, -1)  # 所有待执行任务
        self.re.delete(rl)
        all_task = list(set(rl_task).union(set(ss_task)))

        context = (baId, need)
        need_new_se_task = self.pick_task(all_task, context, (seId,))
        if need_new_se_task:
            one = list(need_new_se_task)[0]
            if self.re.zscore("material", one) is None:
                self.insert_task(one)
        else:
            new_lc_task = self.pick_task(all_task, context, lc_tuple)
            self.insert_task(new_lc_task)
        self.insert_task(rl_task)

    def insert_task(self, tup, count=0):  # 插入任务
        if not tup:
            return
        pipe = self.re.pipeline()
        if type(tup) != str:
            for each in tup:
                if type(each) == tuple:
                    com, count = each
                    pipe.zadd("material", com, count)
                else:
                    pipe.zadd("material", each, 0)
        else:
            pipe.zadd("material", tup, 0)
        pipe.execute()

    def pick_task(self, all_task, args, tup):
        """
            首先通过任务id列表创建任务语句，组成集合A
            随后与已存在任务集合，all_task(B)进行差集运算
            得出需要新加入的任务，放入集合C
            返回集合C
        :param all_task: 全部已存在任务
        :param args: 上下文条件
        :param tup: 任务id列表
        :return: 需要新增的任务列表
        """
        baId, need = args
        # A = []  #
        B = set(all_task)
        A = set()  # 与B进行差集运算的集合
        for need_id in tup:
            mode = "lc"
            if type(need_id) is dict:
                need_id = need_id["lcId"]
            elif need_id[0:2] == "SE":
                mode = "se"

            """
                注：任务需要开关互斥，开命令与关命令不可同时存在，如果存在，则以最新的命令为标准
                此处返回两个字符串是同一ID的开关命令
                同时加入A集合
                差集运算后将得出目前存在的命令类型
                如果两个命令都存在，代表目前不存在任何形式的命令，则选择与need相同的命令
            """
            token = stringUtil.build_switch_command(baId, need, need_id, mode)
            # result = set(token) - B
            # if len(result) == 2:
            A.add(token)
            # else:
            #     B.remove(token[0 if need == 1 else 0])
            #     C.add(token[need])

        return A - B  # 差集
